﻿#pragma warning(disable:4005)
#include <drogon/HttpController.h>
#include "OCMain/Environment.h"
#include "OCMain/StringUtil.h"
#include "OCMain/FileUtil.h"
#include "OCServer/ServerUtil.h"
#include "OCServer/MapManager.h"
#include "OCServer/DocGenerate.h"

using namespace OCPP;
using namespace drogon;
using namespace drogon::orm;

inline void setGZIp(HttpResponsePtr& resp)
{
	resp->addHeader("Content-Encoding", "gzip");
}

inline ContentType getContentType(String layer, String ext)
{
	ContentType contentType = CT_NONE;
	static std::map<String, ContentType> caches;
	auto it = caches.find(layer);
	if (it != caches.end())
	{
		contentType = it->second;
	}
	else
	{
		if (ext == "png")
		{
			contentType = CT_IMAGE_PNG;
		}
		else if (ext == "jpg")
		{
			contentType = CT_IMAGE_JPG;
		}
		else if (ext == "gz")
		{
			contentType = CT_APPLICATION_OCTET_STREAM;
		}
		caches[layer] = contentType;
	}

	return contentType;
}

class MapServer : public HttpController<MapServer>
{
public:
	METHOD_LIST_BEGIN
		METHOD_ADD(MapServer::home, "", Get);
	METHOD_ADD(MapServer::doc, "/doc?query={}", Get);
	METHOD_ADD(MapServer::Map, "{layer}/{z}/{y}/{x}", { Get });
	METHOD_ADD(MapServer::Mvt, "Mvt/{layer}/{z}/{y}/{x}", { Get });
	METHOD_ADD(MapServer::CesT1, "CesT/terrain?l={z}&y={y}&x={x}", { Get });
	METHOD_ADD(MapServer::CesT2, "CesT/{layer}", { Get });
	METHOD_LIST_END
public:
	String startTime;
	MapServer()
	{
		startTime = StringUtil::getTime();
	}
protected:
	void home(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback)
	{
		auto resp = drogon::HttpResponse::newHttpResponse();
		resp->setBody(String("<p>Service start at ") + startTime + "</p>");
		callback(resp);
	}
	void doc(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback,
		String queryapi )
	{
		if (queryapi == "")
		{
			auto resp = drogon::HttpResponse::newRedirectionResponse("/swagger/index.html?s=MapServer");
			callback(resp);
		}
		else if (queryapi == "api")
		{
			OpenApiDoc doc;
			doc.setBaseUrl("/MapServer");
			doc.addMethod().setPath("/{layer}/{z}/{y}/{x}").setDescription("获取列表")
				.addParameter("layer", "layer", "path", "string", "World_Hillshade")
				.addParameter("z", "等级", "path", "int", "1")
				.addParameter("y", "行号", "path", "int", "1")
				.addParameter("x", "列号", "path", "int", "1");
			static String body = doc.toString();
			HttpResponsePtr resp = HttpResponse::newHttpResponse();
			resp->setBody(body);
			resp->setContentTypeCode(drogon::ContentType::CT_APPLICATION_JSON);
			callback(resp);
		}
		else
		{
			auto resp = HttpResponse::newNotFoundResponse();
			callback(resp);
		}
	}

	void Mvt(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback,
		String layer,
		uint32 z, uint32 y, uint32 x)
	{
		HttpResponsePtr resp = HttpResponse::newHttpResponse();

		auto data = Singleton(MapManager).getMvt(layer, z, y, x);

		if (data.empty()) {
			data = "empty";
		}

		resp->setBody(data);
		resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM);

		callback(resp);
	}

	inline bool isGZBinary(const String& bin)
	{
		if (bin.size() < 2) return false;
		return uint8(bin[0]) == 0x1f && uint8(bin[1]) == 0x8b;
	}

	void Map(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback,
		String layer,
		uint32 z, uint32 y, uint32 x)
	{
		HttpResponsePtr resp = HttpResponse::newHttpResponse();
		auto data = Singleton(MapManager).getMap(layer, z, y, x);
		if (!data.empty())
		{
			auto ext = Singleton(MapManager).getExt(layer);
			String name = OCPP::Stringify() << x << "." << ext;
			resp = drogon::HttpResponse::newFileResponse((const unsigned char*)data.data(), data.length(), name,
				CT_APPLICATION_OCTET_STREAM);
			if (ext == "gz" || (ext == "pbf" && isGZBinary(data)))
			{
				setGZIp(resp);
			}
			else
			{
				resp->setContentTypeCode(getContentType(layer, ext));
			}
		}

		callback(resp);
	}

	void CesT2(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback,
		String layer)
	{
		HttpResponsePtr resp = HttpResponse::newHttpResponse();
		try {
			String fileName = joinPath(Singleton(Environment).getAppPath(), "CesT.json");
			String content = FileUtil::getFileBinary(fileName);
			resp->setBody(content);
			resp->setContentTypeCode(CT_APPLICATION_JSON);
		}
		catch (UnexpectedRows e) {
			LOG_ERROR << e.what();
		}

		callback(resp);
	}

	void CesT1(const HttpRequestPtr& req,
		std::function<void(const HttpResponsePtr&)>&& callback,
		uint32 z, uint32 y, uint32 x)
	{
		HttpResponsePtr resp = HttpResponse::newHttpResponse();

		auto data = Singleton(MapManager).getMap("CesT", z, y, x);
		if (!data.empty())
		{
			String name = OCPP::Stringify() << x;
			resp = drogon::HttpResponse::newFileResponse((const unsigned char*)data.data(), data.length(), name,
				CT_APPLICATION_OCTET_STREAM);
			setGZIp(resp);
		}

		callback(resp);
	}
};